package module

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cast"
	etcdv3 "go.etcd.io/etcd/client/v3"
	"strings"
)

type server struct {
	Op       int    `json:"Op"`
	Addr     string `json:"Addr"`
	Metadata struct {
		Project string   `json:"project"`
		MType   string   `json:"mtype"`
		Name    string   `json:"name"`
		SType   []string `json:"stype"`
		Replica uint64   `json:"replica"`
	} `json:"Metadata"`
}

func (s *server) Project() string {
	return s.Metadata.Project
}

func (s *server) Address() string {
	return fmt.Sprintf("etcd:///resolver/%s/grpc/%s/%s", s.Project(), s.ModuleType(), s.ModuleName())
}

func (s *server) ModuleType() string {
	return s.Metadata.MType
}

func (s *server) ModuleName() string {
	return s.Metadata.Name
}

func (s *server) Replica() uint64 {
	return s.Metadata.Replica
}

func (s *server) SearchTypes() []string {
	return s.Metadata.SType
}

func (m *manager) start() {
	var (
		err  error
		resp *etcdv3.GetResponse
		key  = fmt.Sprintf("resolver/%s/grpc/", m.project)
	)

	if resp, err = m.client.Get(context.TODO(), key, etcdv3.WithPrefix()); err != nil {
		logrus.Panicf("etcd get[%s] err: %v", key, err)
	}

	for idx := range resp.Kvs {
		var (
			project, mtype, mname string
			replica               uint64
		)

		if project, mtype, mname, replica, err = parseEtcdKey(string(resp.Kvs[idx].Key)); err != nil {
			continue
		}

		srv := new(server)
		if err = json.Unmarshal(resp.Kvs[idx].Value, srv); err != nil {
			logrus.Warnf("module[key: %s] unmarshal err, value: %s, err: %v", resp.Kvs[idx].Key, resp.Kvs[idx].Value, err)
			continue
		}

		if srv.Project() != project || srv.Metadata.MType != mtype || srv.Metadata.Name != mname || srv.Metadata.Replica != replica {
			logrus.Warnf("module[key: %s] unmarshal result invalid, value: %s", resp.Kvs[idx].Key, resp.Kvs[idx].Value)
			continue
		}

		m.Registry(srv)
	}
}

func (m *manager) watch() {

	var (
		err error
		wch = m.client.Watch(m.ctx, fmt.Sprintf("resolver/%s/grpc/", m.project), etcdv3.WithPrefix())
	)

	go func() {
		for wResp := range wch {
			for _, event := range wResp.Events {
				var (
					project, moduleType, moduleName string
					replica                         uint64
				)

				if project, moduleType, moduleName, replica, err = parseEtcdKey(string(event.Kv.Key)); err != nil {
					continue
				}

				if len(event.Kv.Value) == 0 {
					logrus.Warnf("module[key: %s] value is nil, trying remove from module ctrl", event.Kv.Key)

					m.Unregister(moduleType, moduleName, replica)
					continue
				}

				srv := new(server)
				if err = json.Unmarshal(event.Kv.Value, srv); err != nil {
					logrus.Warnf("module[key: %s] unmarshal err, value: %s, err: %v", event.Kv.Key, event.Kv.Value, err)
					continue
				}

				if srv.Project() != project || srv.Metadata.MType != moduleType || srv.Metadata.Name != moduleName || srv.Metadata.Replica != replica {
					logrus.Warnf("module[key: %s] unmarshal result invalid, value: %s", event.Kv.Key, event.Kv.Value)
					continue
				}

				m.Registry(srv)
			}

		}

		logrus.Warn("watch channel closed")
	}()
}

func parseEtcdKey(key string) (project string, moduleType string, moduleName string, replica uint64, err error) {
	keys := strings.Split(key, "/")
	if len(keys) != 6 {
		err = fmt.Errorf("parse etcd resolver key[%s] invalid, split != 6", key)
		logrus.Error(err)
		return
	}

	if replica, err = cast.ToUint64E(keys[5]); err != nil {
		err = fmt.Errorf("parse etcd resolver key[%s] err: %w", key, err)
		logrus.Error(err)
		return
	}

	project, moduleType, moduleName = keys[1], keys[3], keys[4]

	return
}
